using System;
using System.Text;
using System.IO;
using System.Collections.Generic;

namespace CSVKit {

    /**
     * Represents an entire CSV file in a table format.  Tries to quote
     * strings properly.
     * 
     * @author kris
     */
    public class CSVTable {

        List<List<String>> rows;

        /**
         * Initializes an empty CSVTable.
         */
        public CSVTable() {
            rows = new List<List<String>>();
        }

        public CSVTable(String readFile)
        {
            rows = new List<List<String>>();
            this.readCSV(readFile);
        }

        /**
         * Loads table data from a CSV file.
         * <p>
         * Does not read trailing null cells.
         * <p>
         * Inspired by, but heavily modified from:
         * <a href="http://www.java-examples.com/parse-csv-file-using-stringtokenizer-example">
         * http://www.java-examples.com/parse-csv-file-using-stringtokenizer-example</a>
         * 
         * @param readFile A file in CSV format to be loaded into the table.
         * @throws IOException
         */
        public void readCSV(String readFile) {
            //start with a clean table
            rows.Clear();

            // create BufferedReader to read csv file
            StreamReader br = new StreamReader(readFile);
            String strLine = "", thisCell = "", cellBuffer;
            bool esc = false;
            String[] st;
            List<String> thisRow = new List<String>();

            // read comma separated file line by line
            while ((strLine = br.ReadLine()) != null) {
                // break comma separated line using ","
                st = strLine.Split(',');
            
                // read all tokens into the row
                for (int i = 0; i < st.Length; i++) {
                    // grab a token
                    cellBuffer = st[i];

                    // is it quoted?
                    if (esc) {
                        thisCell += ",";
                    } else {
                        if (cellBuffer.Length > 0 && cellBuffer[ 0 ] == '"') {
                            // remove quote at beginning
                            cellBuffer = cellBuffer.Substring(1);
                            // start of quoted string
                            esc = true;
                        }
                    }

                    // check for terminating quotation mark
                    if (cellBuffer.Length > 0 && cellBuffer[ cellBuffer.Length - 1 ] == '"') {
                        // remove quote at end
                        cellBuffer = cellBuffer.Substring(0, cellBuffer.Length - 1);
                        // end of quoted string
                        esc = false;
                    }

                    // append to current cell, normalizing quotation marks
                    thisCell += cellBuffer.Replace("\"\"", "\"");

                    if (!esc) {
                        // add the complete cell to the row
                        thisRow.Add(thisCell);
                        thisCell = "";
                    }
                }

                // add the row to the rows array
                rows.Add(thisRow);
                thisRow = new List<String>();
            }
            br.Close();
        }

        /**
         * Writes table data into a CSV formatted file.
         * <p>
         * The file will be truncated if it exists.
         * 
         * @param writeFile The CSV file to be written.
         * @throws IOException 
         */
        public void writeCSV(String writeFile) {
            StreamWriter bw = new StreamWriter(writeFile);
            IEnumerator<List<String>> rowIterator = rows.GetEnumerator();
            IEnumerator<String> cellIterator;

            // for each row in the table
            while (rowIterator.MoveNext()) {

                // grab a cell from the current row
                cellIterator = rowIterator.Current.GetEnumerator();
                String thisCell;
                bool firstRun = true;
                while (cellIterator.MoveNext()) // write the cell to the file
                {
                    // get next cell
                    thisCell = cellIterator.Current;

                    // check for special characters
                    if (thisCell.Contains(",") || thisCell.Contains("\n")
                            || thisCell.Contains("\r") || thisCell.Contains("\"")) {
                        // escape special characters
                        thisCell.Replace("\"", "\"\"");
                        thisCell = "\"" + thisCell + "\"";
                    }

                    // make sure a comma is not inserted before the first cell
                    if (firstRun) {
                        firstRun = false;
                    } else {
                        bw.Write(",");
                    }

                    // write one cell to the file
                    bw.Write(thisCell);
                }
                bw.WriteLine(bw.NewLine);
            }
            bw.Close();
        }

        /**
         * Adds a row to the bottom of the table.
         * <p>
         * Quotes are escaped.
         * 
         * @param newRow An ArrayList of Strings.  Each item in the ArrayList
         * will be added as a separate cell in the order of the list.
         */
        public void addRow(List<String> newRow) {
            rows.Add(newRow);
        }

        /**
         * Adds an object to the table as a complete row.
         * 
         * @param newRow Any object implementing the CSVRow interface.
         * 
         * @see CSVRow
         */
        public void addRow(CSVRow newRow) {
            rows.Add(newRow.getCSVRow());
        }

        /**
         * Returns an iterator over the rows in the table.
         * <p>
         * Accessing by iterator permits modifying table data.
         * 
         * @return An iterator over the list of rows.
         */
        public IEnumerator<List<String>> getRowIterator() {
            return rows.GetEnumerator();
        }

        /**
         * Completely flushes the table data.
         */
        public void clearTable() {
            rows.Clear();
        }
    }

    /**
     * Abstracts a CSVTable row.
     *
     * @see CSVTable
     * 
     * @author cs463002_10
     */
    public interface CSVRow
    {
        /**
         * Returns a row suitable for import into a CSVTable.
         * <p>
         * There is no restriction on cell ordering; it may differ from what
         * setCSVRow expects.
         * 
         * @return Returns a list of raw strings.  Each String in the list
         * represents an un-escaped cell.
         */
        List<String> getCSVRow();

        /**
         * Interprets a CVSTable row.  Implementing classes read the cells
         * and set data members accordingly.
         * <p>
         * There is no restriction on cell ordering; it may differ from what
         * getCSVRow() returns.
         * 
         * @param setRow A list of raw strings.  Each string in the list
         * represents an un-escaped cell.
         */
        void setCSVRow(List<String> setRow);
    }
}
